package edu.stanford.bmir.protege.web.server.stkosutil.enricher;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;

import org.semanticweb.owlapi.model.AddAxiom;
import org.semanticweb.owlapi.model.EntityType;
import org.semanticweb.owlapi.model.OWLAnnotation;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLDataProperty;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLOntologyChange;
import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
import edu.stanford.bmir.protege.web.client.rpc.data.Triple;

import edu.stanford.bmir.protege.web.client.rpc.stkosutil.common.MatchLevel;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.common.TripleData;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.common.UtilConstants;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.common.UtilEntityType;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.enricher.CandidateEntity;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.enricher.EnricherSource;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.enricher.EnrichmentOptions;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.enricher.EnrichmentResult;
import edu.stanford.bmir.protege.web.server.change.ChangeApplicationResult;
import edu.stanford.bmir.protege.web.server.change.ChangeDescriptionGenerator;
import edu.stanford.bmir.protege.web.server.change.FixedMessageChangeDescriptionGenerator;
import edu.stanford.bmir.protege.web.server.owlapi.AnnotationsTreatment;
import edu.stanford.bmir.protege.web.server.owlapi.NonAnnotationTreatment;
import edu.stanford.bmir.protege.web.server.owlapi.OWLAPIProject;
import edu.stanford.bmir.protege.web.server.owlapi.OWLAPIProjectManager;
import edu.stanford.bmir.protege.web.server.owlapi.RenderingManager;
import edu.stanford.bmir.protege.web.server.owlapi.TripleMapper;
import edu.stanford.bmir.protege.web.server.owlapi.TripleMapperSelector;
import edu.stanford.bmir.protege.web.server.stkosutil.common.AppException;
import edu.stanford.bmir.protege.web.server.stkosutil.enricher.CandidateOPChangeGenerator;
import edu.stanford.bmir.protege.web.server.stkosutil.mapper.OwlClassHolder;
import edu.stanford.bmir.protege.web.server.stkosutil.mapper.OwlClassHolderComparator;
import edu.stanford.bmir.protege.web.server.stkosutil.mapper.OwlDataPropertyHolder;
import edu.stanford.bmir.protege.web.server.stkosutil.mapper.OwlDataPropertyHolderComparator;
import edu.stanford.bmir.protege.web.server.stkosutil.mapper.OwlObjectPropertyHolder;
import edu.stanford.bmir.protege.web.server.stkosutil.mapper.OwlObjectPropertyHolderComparator;
import edu.stanford.bmir.protege.web.shared.project.ProjectId;
import edu.stanford.bmir.protege.web.shared.user.UserId;

public class PropertyEnricher {
	private ArrayList<OwlClassHolder> _alClassByBT = null;
	private ArrayList<OwlClassHolder> _alClassByLowerBT = null;
	private ArrayList<OwlObjectPropertyHolder> _alObjectProperyByBT = null;
	private ArrayList<OwlObjectPropertyHolder> _alObjectProperyByLowerBT = null;
	private ArrayList<OwlDataPropertyHolder> _alDataProperyByBT = null;
	private ArrayList<OwlDataPropertyHolder> _alDataProperyByLowerBT = null;

	private int _newClassCount = 0 ;
	private int _newPropertyCount = 0;
	private int _newClassCurb = 50;
	private int _newPropertyCurb = 50;
	private int _newTriplePerClassCurb = 5;
	private OWLAPIProject getProject(String projectName) {
        if (projectName == null) {
            throw new NullPointerException("projectName must not be null");
        }
        ProjectId projectId = ProjectId.get(projectName);
        // TODO: Log
        return getProject(projectId);
    }

    private OWLAPIProject getProject(ProjectId projectId) {
        OWLAPIProjectManager pm = OWLAPIProjectManager.getProjectManager();
        return pm.getProject(projectId);
    }
    
	public List<Triple> getEntityTriples(final String projectName, String entityName) {
	        final Set<Triple> result = new LinkedHashSet<Triple>();
	        TripleMapperSelector selector = new TripleMapperSelector(getProject(projectName), AnnotationsTreatment.EXCLUDE_ANNOTATIONS, NonAnnotationTreatment.INCLUDE_NON_ANNOTATIONS);
	        OWLAPIProject project = getProject(projectName);
	        Set<OWLEntity> entities = project.getRenderingManager().getEntities(entityName);
	        for (OWLEntity entity : entities) {
	            TripleMapper<?> mapper = selector.getMapper(entity);
	            if (mapper != null) {
	                result.addAll(mapper.getTriples());
	            }
	        }
	        return new ArrayList<Triple>(result);
	  }
	  public EnrichmentResult enrichOntolgoy(String projectName, UserId userId) throws AppException{
		  //String entityName = "http://webprotege.stanford.edu/R7SHi3IfG3Q3oX8TKhJvygP";
		 // String entityName = "http://webprotege.stanford.edu/RBeGrdhtbgTCunHkKjpK6hY";
		 // List<Triple> lTriples = this.getEntityTriples(projectName, entityName);
		 // System.out.println(lTriples.size());
		  
		  EnrichmentResult result = new EnrichmentResult();
		  OWLAPIProject project = this.getProject(projectName);
		  KnowlegeBaseClient client = new KnowlegeBaseClient();
		  loadClassHolders(project);
		  loadDataPropertyHolders(project);
		  loadObjectPropertyHolders(project);
		 // _sObjectBrowserTexts = this.loadObjectPropertyBrowserText(project);
		 // _sDataPropertyBrowserTexts = this.loadDataPropertyBrowserText(project);
		  ArrayList<OwlClassHolder> classHolders = new ArrayList<OwlClassHolder>();
		  classHolders.addAll(this._alClassByLowerBT);
		  //DataSource dataSource = new DataSource();
		  EnrichmentOptions options = new EnrichmentOptions();
		  options.setSource(EnricherSource.DOC_TRIPLE);
		  for (OwlClassHolder classHolder: classHolders){
			  String className = classHolder.getName();
			  String browserText = classHolder.getBrowserText();
			  if (browserText.compareTo("Flight")==0){
				  System.out.println("Found it");
			  }
			  List<TripleData> candidateTriples = client.fetchTripleDataList(browserText, options);
			  if (candidateTriples==null || candidateTriples.size()==0){
				  continue;
			  }
			  List<Triple> currentTriples = this.getEntityTriples(projectName, className);
			  enrichClass(project, classHolder, currentTriples, candidateTriples, userId);
		  }
		  return result;
	  }
	  
	  private void enrichClass(OWLAPIProject project, OwlClassHolder classHolder, List<Triple> currentTriples, List<TripleData> candidateTriples, UserId userId){
		  List<TripleData> freshTriples = this.extractFreshTriples(currentTriples, candidateTriples);
		  if (freshTriples!=null && freshTriples.size()>0){
			  int newTripleCount = 0;
			  for (TripleData freshTriple: freshTriples){
				  if (newTripleCount<=_newTriplePerClassCurb){
					  boolean tripleCreated = createNewTriple(project, classHolder, freshTriple, userId);
					  if (tripleCreated){
						  newTripleCount++;
					  }
				  }
			  }
		  }
	  }
	  private List<TripleData> extractFreshTriples(List<Triple> currentTriples, List<TripleData> candidateTriples){
		  List<TripleData> alFreshTripleData = new ArrayList<TripleData>();
		  for (TripleData candidateTriple: candidateTriples){
			  boolean bExists = false;
			  for (Triple currentTriple: currentTriples){
				  if (currentTriple.getProperty().getBrowserText().equalsIgnoreCase(candidateTriple.getPredicate())
					&& currentTriple.getValue().getBrowserText().equalsIgnoreCase(candidateTriple.getObject())
						  ){
					  bExists = true;
				  }
			  }
			  if (bExists==false){
				  alFreshTripleData.add(candidateTriple);
			  }
		  }
		  return alFreshTripleData;
	  } 
	  private boolean createNewTriple(OWLAPIProject project, OwlClassHolder subjectClassHolder, TripleData freshTriple, UserId userId){
		 
		  String predicateBrowserText = freshTriple.getPredicate();
		  String objectBrowserText = freshTriple.getObject();
		  OwlDataPropertyHolder dpHolderSeed = new OwlDataPropertyHolder();
		  dpHolderSeed.setBrowserText(predicateBrowserText);
		  OwlDataPropertyHolderComparator dpComparator = new OwlDataPropertyHolderComparator(MatchLevel.CASE_INSENSITIVE);
		  int pos = Collections.binarySearch(this._alDataProperyByLowerBT, dpHolderSeed, dpComparator);
		  //暂不处理DataProerty;
		  if (pos>=0){
			  return false;
		  }
		  boolean bPropertyExists = false;
		  OwlObjectPropertyHolder targetPropertyHolder = null;
		  pos = -1;
		  OwlObjectPropertyHolder opHolderSeed = new OwlObjectPropertyHolder();
		  opHolderSeed.setBrowserText(predicateBrowserText);
		  OwlObjectPropertyHolderComparator opComparator = new OwlObjectPropertyHolderComparator(MatchLevel.CASE_SENSITIVE);
		  pos = Collections.binarySearch(this._alObjectProperyByBT, opHolderSeed, opComparator);
		  if (pos>=0){
			  bPropertyExists = true;
			  targetPropertyHolder = _alObjectProperyByBT.get(pos);
		  }else{
			  opComparator = new OwlObjectPropertyHolderComparator(MatchLevel.CASE_INSENSITIVE);
			  pos = Collections.binarySearch(this._alObjectProperyByLowerBT, opHolderSeed, opComparator);
			  if (pos>=0){
				  bPropertyExists = true;
				  targetPropertyHolder = _alObjectProperyByLowerBT.get(pos);
			  }
		  }
		  if (bPropertyExists == false){
			  //create property;
		  }
		  boolean bObjectClassExists = false;
		  OwlClassHolder targetObjectClassHolder = null;
		  pos = -1;
		  OwlClassHolder classHolderSeed = new OwlClassHolder();
		  classHolderSeed.setBrowserText(objectBrowserText);
		  OwlClassHolderComparator classComparator = new OwlClassHolderComparator(MatchLevel.CASE_SENSITIVE);
		  pos = Collections.binarySearch(this._alClassByLowerBT, classHolderSeed, classComparator);
		  if (pos>=0){
			  bObjectClassExists = true;
			  targetObjectClassHolder = _alClassByLowerBT.get(pos);
		  }else{
			  classComparator = new OwlClassHolderComparator(MatchLevel.CASE_INSENSITIVE);
			  pos = Collections.binarySearch(this._alClassByLowerBT, classHolderSeed, classComparator);
			  if (pos>=0){
				  bObjectClassExists = true;
				  targetObjectClassHolder = _alClassByLowerBT.get(pos);
			  }
		  }
		  if ((bPropertyExists == false && this._newPropertyCount>=this._newPropertyCurb) ||
				(bObjectClassExists == false && this._newClassCount>=this._newClassCurb)){
			  return false;
		  }
		  
		  if (bPropertyExists == false){
			  //create property;
			  targetPropertyHolder = this.createNewObjectProperty(project, predicateBrowserText, userId);
			  
			  opComparator.setMatchLevel(MatchLevel.CASE_SENSITIVE);
			  pos = Collections.binarySearch(this._alObjectProperyByBT, targetPropertyHolder, opComparator);
			  _alObjectProperyByBT.add((pos + 1) * -1, targetPropertyHolder);
			  
			  opComparator.setMatchLevel(MatchLevel.CASE_INSENSITIVE);
			  pos = Collections.binarySearch(this._alObjectProperyByLowerBT, targetPropertyHolder, opComparator);
			  _alObjectProperyByLowerBT.add((pos + 1) * -1, targetPropertyHolder);
		  }
		  if (bObjectClassExists == false){
			  targetObjectClassHolder = this.createNewClass(project, objectBrowserText, userId);
			  
			  classComparator.setMatchLevel(MatchLevel.CASE_SENSITIVE);
			  pos = Collections.binarySearch(this._alClassByBT, targetObjectClassHolder, classComparator);
			  _alClassByBT.add((pos + 1) * -1, targetObjectClassHolder);
			  
			  classComparator.setMatchLevel(MatchLevel.CASE_INSENSITIVE);
			  pos = Collections.binarySearch(this._alClassByLowerBT, targetObjectClassHolder, classComparator);
			  _alClassByLowerBT.add((pos + 1) * -1, targetObjectClassHolder);
			  
		  }
		  OWLDataFactory df = project.getDataFactory();
		 
		  OWLClassExpression hasSome =
				  df.getOWLObjectSomeValuesFrom(targetPropertyHolder.getOwlObjectProperty(), targetObjectClassHolder.getOwlClass());
		  
		  String from="Document Triples";
		  String annotaionValue= UtilConstants.FLAG_ENRICHED + ": Y" + "; " + UtilConstants.FLAG_FROM + ": " + from;
		  OWLAnnotation commentAnno = df.getOWLAnnotation(
					 df.getRDFSComment(),
					 df.getOWLLiteral(annotaionValue, "en"));
		  Set<OWLAnnotation> sAnnos = new HashSet<OWLAnnotation>();	
		  sAnnos.add(commentAnno);
		  OWLSubClassOfAxiom ax =
				  df.getOWLSubClassOfAxiom(subjectClassHolder.getOwlClass(), hasSome,sAnnos);
		  List<OWLOntologyChange> changeList = new ArrayList<OWLOntologyChange>();	
		  AddAxiom addAxiom =  new AddAxiom(project.getRootOntology(), ax);	 
		  changeList.add(addAxiom);
		  project.applyChanges(userId, changeList, "添加类约束");
		  return true;
	  }
	  private OwlClassHolder createNewClass(OWLAPIProject project, String browserText, UserId userId){
		  	OwlClassHolder holder = new OwlClassHolder();
	    	RenderingManager rm = project.getRenderingManager();
	 	    OWLClass parentClass= project.getDataFactory().getOWLThing();
	 	    ArrayList<CandidateClassHolder> alCandidateClassHolders = new ArrayList<CandidateClassHolder>();
	 	    CandidateClassHolder candidateClassHolder = new CandidateClassHolder();
	 	    candidateClassHolder.setOwlClass(parentClass);
	 	    ArrayList<CandidateEntity> alCandidateEntities = new ArrayList<CandidateEntity>();
	 	    CandidateEntity candidateEntity = new CandidateEntity();
	 	    candidateEntity.setBrowserText(browserText);
	 	    candidateEntity.setEntityType(UtilEntityType.CLASS);
	 	    candidateEntity.setSourceLabel("Document Triples ");
	 	    alCandidateEntities.add(candidateEntity);
	 	    candidateClassHolder.setCandidateChildClassList(alCandidateEntities);
	 	    alCandidateClassHolders.add(candidateClassHolder);
	 	    CandidateClassesChangeGenerator changeGenerator = new CandidateClassesChangeGenerator(alCandidateClassHolders);
	 		ChangeDescriptionGenerator descriptionGenerator = new FixedMessageChangeDescriptionGenerator("本体丰富");
	 		ChangeApplicationResult<Set<OWLObjectProperty>> result2 =  project.applyChanges(userId, changeGenerator, descriptionGenerator);
	 		//RenameMap renameMap = result2.getRenameMap();
	 		HashMap<OWLClass, String> hFreshClassList = changeGenerator.getFresshClassList();
	 		OWLDataFactory df = project.getDataFactory();
	 		List<OWLOntologyChange> changeList = new ArrayList<OWLOntologyChange>();
	 		ArrayList<OWLEntity> alRenamedClassList = new ArrayList<OWLEntity>();
	 		for (Entry<OWLClass, String> entry : hFreshClassList.entrySet()) {
	 			OWLClass freshClass = entry.getKey();
	 			String from = entry.getValue();
	 			OWLEntity renamedClass = result2.getRenamedEntity(freshClass);
	 			String annotaionValue= UtilConstants.FLAG_ENRICHED + ": Y" + "; " + UtilConstants.FLAG_FROM + ": " + from;
	 			OWLAnnotation commentAnno = df.getOWLAnnotation(
	 					 df.getRDFSComment(),
	 					 df.getOWLLiteral(annotaionValue, "en"));
	 			
	 			OWLAxiom ax = df.getOWLAnnotationAssertionAxiom(renamedClass.getIRI(), commentAnno);
	 			AddAxiom addAxiom =  new AddAxiom(project.getRootOntology(), ax);	 
	 			changeList.add(addAxiom);
	 			alRenamedClassList.add(renamedClass);
	 		}
	 		project.applyChanges(userId, changeList, "添加annotaion");
	 		holder.setBrowserText(browserText);
	 		holder.setName(alRenamedClassList.get(0).getIRI().toString());
	 		holder.setOwlClass((OWLClass)alRenamedClassList.get(0));
	 		this._newClassCount++;
	 		return holder;	
	  }
	  
	  private OwlObjectPropertyHolder createNewObjectProperty(OWLAPIProject project, String browserText, UserId userId){
		  	OwlObjectPropertyHolder holder = new OwlObjectPropertyHolder();
	    	RenderingManager rm = project.getRenderingManager();
	 	    OWLObjectProperty parentProperty = project.getDataFactory().getOWLTopObjectProperty();
	 	    
	 	    
	 	    ArrayList<CandidateEntity> alCandidateEntities = new ArrayList<CandidateEntity>();
	 	    CandidateEntity candidateEntity = new CandidateEntity();
	 	    candidateEntity.setBrowserText(browserText);
	 	    candidateEntity.setEntityType(UtilEntityType.OBJECT_PROPERTY);
	 	    candidateEntity.setSourceLabel("Document Triples ");
	 	    alCandidateEntities.add(candidateEntity);
	 	   
	 	    CandidateOPChangeGenerator changeGenerator = new CandidateOPChangeGenerator(parentProperty, alCandidateEntities);
	 		ChangeDescriptionGenerator descriptionGenerator = new FixedMessageChangeDescriptionGenerator("新对象属性");
	 		ChangeApplicationResult result2 =  project.applyChanges(userId, changeGenerator, descriptionGenerator);
	 		//RenameMap renameMap = result2.getRenameMap();
	 		HashMap<OWLObjectProperty, String> hFreshPropertyList = changeGenerator.getFresshPropertyList();
	 		OWLDataFactory df = project.getDataFactory();
	 		List<OWLOntologyChange> changeList = new ArrayList<OWLOntologyChange>();
	 		ArrayList<OWLEntity> alRenamedPropertyList = new ArrayList<OWLEntity>();
	 		for (Entry<OWLObjectProperty, String> entry : hFreshPropertyList.entrySet()) {
	 			OWLObjectProperty freshProperty = entry.getKey();
	 			String from = entry.getValue();
	 			OWLEntity renamedProperty = result2.getRenamedEntity(freshProperty);
	 			String annotaionValue= UtilConstants.FLAG_ENRICHED + ": Y" + "; " + UtilConstants.FLAG_FROM + ": " + from;
	 			OWLAnnotation commentAnno = df.getOWLAnnotation(
	 					 df.getRDFSComment(),
	 					 df.getOWLLiteral(annotaionValue, "en"));
	 			
	 			OWLAxiom ax = df.getOWLAnnotationAssertionAxiom(renamedProperty.getIRI(), commentAnno);
	 			AddAxiom addAxiom =  new AddAxiom(project.getRootOntology(), ax);	 
	 			changeList.add(addAxiom);
	 			alRenamedPropertyList.add(renamedProperty);
	 		}
	 		project.applyChanges(userId, changeList, "添加annotaion");
	 		holder.setBrowserText(browserText);
	 		holder.setName(alRenamedPropertyList.get(0).getIRI().toString());
	 		holder.setOwlObjectProperty((OWLObjectProperty)alRenamedPropertyList.get(0));
	 		this._newPropertyCount++;
	 		return holder;	
	  }
	  private void loadClassHolders(OWLAPIProject project){
		  _alClassByBT = new ArrayList<OwlClassHolder>();
		  _alClassByLowerBT = new ArrayList<OwlClassHolder>();
		  RenderingManager rm = project.getRenderingManager();
		  Set<OWLClass> sClassList = project.getRootOntology().getClassesInSignature();
		
		  for (OWLClass cls: sClassList){
			  String browserText = rm.getBrowserText(cls);
			  OwlClassHolder holder = new OwlClassHolder();
			  holder.setBrowserText(browserText);
			  holder.setName(cls.getIRI().toString());
			  holder.setOwlClass(cls);
			  _alClassByBT.add(holder);
			  _alClassByLowerBT.add(holder);
		  }
		  OwlClassHolderComparator comparator = new OwlClassHolderComparator(MatchLevel.CASE_SENSITIVE);
		  Collections.sort(_alClassByBT, comparator);
		  comparator = new OwlClassHolderComparator(MatchLevel.CASE_INSENSITIVE);
		  Collections.sort(_alClassByLowerBT, comparator);
		 
	  }
	  private void loadDataPropertyHolders( OWLAPIProject project){
		  _alDataProperyByBT = new ArrayList<OwlDataPropertyHolder>();
		  _alDataProperyByLowerBT = new ArrayList<OwlDataPropertyHolder>();
		  RenderingManager rm = project.getRenderingManager();
		  Set<OWLDataProperty> sPropertyList = project.getRootOntology().getDataPropertiesInSignature();
		  for (OWLDataProperty property: sPropertyList){
			  OwlDataPropertyHolder holder = new OwlDataPropertyHolder();
			  String entityName = property.getIRI().toString();
			  String browserText = rm.getBrowserText(property);
			  holder.setBrowserText(browserText);
			  holder.setName(entityName);
			  holder.setOwlDataProperty(property);
			  this._alDataProperyByBT.add(holder);
			  this._alDataProperyByLowerBT.add(holder);
		  }
		  OwlDataPropertyHolderComparator comparator = new OwlDataPropertyHolderComparator(MatchLevel.CASE_SENSITIVE);
		  Collections.sort(_alDataProperyByBT, comparator);
		  comparator = new OwlDataPropertyHolderComparator(MatchLevel.CASE_INSENSITIVE);
		  Collections.sort(_alDataProperyByLowerBT, comparator);	  
	  }
	  
	  private void loadObjectPropertyHolders(OWLAPIProject project){
		  this._alObjectProperyByBT = new ArrayList<OwlObjectPropertyHolder>();
		  this._alObjectProperyByLowerBT = new ArrayList<OwlObjectPropertyHolder>();
		  RenderingManager rm = project.getRenderingManager();
		  Set<OWLObjectProperty> sPropertyList = project.getRootOntology().getObjectPropertiesInSignature();
		  for (OWLObjectProperty property: sPropertyList){
			  OwlObjectPropertyHolder holder = new OwlObjectPropertyHolder();
			  String entityName = property.getIRI().toString();
			  String browserText = rm.getBrowserText(property);
			  holder.setBrowserText(browserText);
			  holder.setName(entityName);
			  holder.setOwlObjectProperty(property);;
			  this._alObjectProperyByBT.add(holder);
			  this._alObjectProperyByLowerBT.add(holder);
		  }
		  OwlObjectPropertyHolderComparator comparator = new OwlObjectPropertyHolderComparator(MatchLevel.CASE_SENSITIVE);
		  Collections.sort(_alObjectProperyByBT, comparator);
		  comparator = new OwlObjectPropertyHolderComparator(MatchLevel.CASE_INSENSITIVE);
		  Collections.sort(_alObjectProperyByLowerBT, comparator);
	  }
	  
	  
}

